package com.fmyexample.v2mcsecuritybootjdbc.sucerity;

import com.alibaba.fastjson.JSONObject;
import com.fmy.mcsecurity.config.McSecurityConfig;
import com.fmy.mcsecurity.config.builders.HttpMcSecurity;
import com.fmy.mcsecurity.integration.ClientSupportIntegration;
import com.fmy.mcsecurity.login.hander.LoginHandler;
import com.fmy.mcsecurity.starter.EnableMcSecurity;
import com.fmy.mcsecurity.token.TokenStorage;
import com.fmy.mcsecurity.token.model.SecurityToken;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsUtils;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Mr.fmy
 * @version 1.0.0
 * @ClassName SecurityAdapterConfig
 * @Description TODO 抽象配置
 * @createTime 2020/6/17 10:36
 */
@EnableMcSecurity
public abstract class SecurityAdapterConfig extends McSecurityConfig {

    /**
     * LOGIN_URL 登录路径
     * AUTH_URL  登录成功后访问的路径
     */
    private final String LOGIN_URL = "/login", AUTH_URL = "/**/auth/**";

    public static final ThreadLocal<String> SALT_HOLDER = new ThreadLocal<>();

    private final UserDetailsService userDetailsService;

    protected SecurityAdapterConfig(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(HttpMcSecurity security) {
        //配置登录额外参数拦截URI，该路径为 `登录URI`
        security.setUserPassParamsMatcher(LOGIN_URL)
                //配置Token填充效验失败后报错URI，该URI为 `必须登录才可访问URI`
                //该参数主要效果为，当用户携带过期token访问不需要登录就可访问的
                //接口时不抛出401异常而影响到用户浏览相关数据
                .setTokenContextUrl(AUTH_URL);
        //和上面一样，false代表取相反URI，true代表拦截传入的URI，默认为true
//        .setTokenContextUrl(false,AUTH_URI);
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置 UserDetailsService
        auth.authenticationProvider(authenticationProvider());
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
                // return ToolKit.md5Salt(SALT_HOLDER.get(), charSequence.toString());
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return true;
                //return ToolKit.md5Salt(SALT_HOLDER.get(), charSequence.toString()).equals(s);
            }
        });
        return provider;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭cors以及csrf
        http.cors().and().csrf().disable()
                //登录路径配置
                .formLogin().loginPage(LOGIN_URL);

        http.authorizeRequests()
                //处理跨域请求中的Preflight请求
                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                //配置需要登录才可访问路径
                .antMatchers(AUTH_URL).authenticated()
                //配置不需要登录就可访问路径
                .antMatchers("/**").permitAll();

        http.exceptionHandling()
                //配置权限不足异常处理
//                .accessDeniedHandler()
                //配置匿名访问异常处理，当token不存在或过期也会触发该方法
                .authenticationEntryPoint((request, response, e) -> {
                    Map<String, Object> result = new HashMap<>(2);
                    result.put("code", "NOT_LOGIN");
                    result.put("msg", "Please log in first");
                    //返回状态码 401
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    response.setCharacterEncoding("utf-8");
                    response.setContentType("application/json; charset=utf-8");
                    try (PrintWriter writer = response.getWriter()) {
                        writer.println(JSONObject.toJSONString(result));
                    }
                });
    }

    @Bean
    public abstract TokenStorage tokenStorage();

    @Bean
    public ClientSupportIntegration clientSupportIntegration() {
        return client -> true;
    }

    /**
     * 配置 {@link LoginHandler}
     */
    @Bean
    public LoginHandler<Map<String, Object>> loginHandler() {
        return new LoginHandler<Map<String, Object>>() {
            /**
             * 登录失败回调、这里可以做处理
             */
            @Override
            public Map<String, Object> resultFail(Exception e) {
                Map<String, Object> result = new HashMap<>(2);
                result.put("code", "LOGIN_FALL");
                result.put("msg", e.getMessage());
                return result;
            }

            @Override
            public Map<String, Object> resultSuccess(SecurityToken token) {
                Map<String, Object> result = new HashMap<>(4);
                result.put("data", token);
                result.put("code", "OK");
                result.put("msg", "登录成功");
                return result;
            }

            @Override
            public void resultPrefix() {
            }
        };
    }
}
